/*
尽可能还原 Promise 中的每一个 API, 并通过注释的方式描述思路和原理.
*/

const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECT = 'reject';

function resolvePromise(promise2, x, resolve, reject) {
  // console.log('promise2 === x', promise2 === x);
  if (promise2 === x) {
    reject(new TypeError('Promise不能返回自身'));
    return;
  }
  if (x instanceof MyPromise) {
    x.then(resolve, reject);
  } else {
    resolve(x);
  }
}

class MyPromise {
  constructor(executor) {
    try {
      executor(this.resolve, this.reject);
    } catch(e) {
      this.reject(e)
    }
  }

  status = PENDING;
  value = undefined;  // 成功之后的值
  reason = undefined; // 失败原因
  successCallbackArray = [];
  failCallbackArray = [];

  resolve = (value) => {
    if (this.status !== PENDING) return;
    this.status = FULFILLED;
    this.value = value;
    while(this.successCallbackArray.length) {
      this.successCallbackArray.shift()();
    }
  }

  reject = (reason) => {
    if (this.status !== PENDING) return;
    this.status = REJECT;
    this.reason = reason;
    while(this.failCallbackArray.length) {
      this.failCallbackArray.shift()();
    }
  }

  then(successCallback, failCallback) {
    successCallback = successCallback ? successCallback : value => value;
    failCallback = failCallback ? failCallback : reason => { throw reason };
    const promise2 = new MyPromise((resolve, reject) => {
      if (this.status === FULFILLED) {
        // promise2如果为同步代码是拿不到promise2的（还没有new）需要改成异步代码
        setTimeout(() => {
          try {
            let x = successCallback(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch(e) {
            reject(e);
          }
        }, 0)
      } else if (this.status === REJECT) {
        setTimeout(() => {
          try {
            let x = failCallback(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch(e) {
            reject(e);
          }
        }, 0)
      } else {  // 异步的情况，此时还在等待，状态status为PENDING，需要把回调存起来
        this.successCallbackArray.push(() => {
          setTimeout(() => {
            try {
              let x = successCallback(this.value);
              resolvePromise(promise2, x, resolve, reject);
            } catch(e) {
              reject(e);
            }
          }, 0)
        });
        this.failCallbackArray.push(() => {
          setTimeout(() => {
            try {
              let x = failCallback(this.reason);
              resolvePromise(promise2, x, resolve, reject);
            } catch(e) {
              reject(e);
            }
          }, 0)
        });
      }
    })
    return promise2;
  }

  catch(failCallback) {
    return this.then(undefined, failCallback);
  }

  finally(callback) {
    return this.then((value) => {
      return MyPromise.resolve(callback()).then(() => value);
    }, (reason) => {
      return MyPromise.resolve(callback()).then(() => { throw reason });
    })
  }

  static resolve(value) {
    if (value instanceof MyPromise) return value;
    return new MyPromise(resolve => resolve(value));
  }

  static all(array) {
    const result = [];
    let count = 0;
    return new MyPromise((resolve, reject) => {
      function addData(index, value) {
        result[index] = value;
        count++;
        if (count === array.length) {
          resolve(result);
        }
      }
      array.forEach((item, index) => {
        if (item instanceof MyPromise) {
          item.then(value => addData(index, value), reason => reject(reason));
        } else {
          addData(index, item);
        }
      })
    })
  }

}

module.exports = {
  MyPromise
}
